import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
import seaborn as sns
# Lectura inicial de los datos
df_kaggle = pd.read_csv('houses_Madrid.csv')
# Dimensiones del dataset
print(df_kaggle.shape)
# Vemos algunos ejemplos
df_kaggle.head(3)
(21742, 58)
| Unnamed: 0 | id | title | subtitle | sq_mt_built | sq_mt_useful | n_rooms | n_bathrooms | n_floors | sq_mt_allotment | ... | energy_certificate | has_parking | has_private_parking | has_public_parking | is_parking_included_in_price | parking_price | is_orientation_north | is_orientation_west | is_orientation_south | is_orientation_east | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | 21742 | Piso en venta en calle de Godella, 64 | San Cristóbal, Madrid | 64.0 | 60.0 | 2 | 1.0 | NaN | NaN | ... | D | False | NaN | NaN | NaN | NaN | False | True | False | False |
| 1 | 1 | 21741 | Piso en venta en calle de la del Manojo de Rosas | Los Ángeles, Madrid | 70.0 | NaN | 3 | 1.0 | NaN | NaN | ... | en trámite | False | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 2 | 2 | 21740 | Piso en venta en calle del Talco, 68 | San Andrés, Madrid | 94.0 | 54.0 | 2 | 2.0 | NaN | NaN | ... | no indicado | False | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 rows × 58 columns
Vemos que son 21.742 registros sobre los que se evalúan 58 características.
Creemos conveniente convertir alguna de las columnas en el índice del dataset, si es posible. A priori, tanto la columna "ID" como "Unnamed: 0" parece que puede ser el índice del conjunto de datos, pues debe ser un identificador único.
Lo comprobamos y vemos que ambas columnas tienen entradas únicas, por lo que podemos definir cualquiera de las dos variables como el índice del dataset.
# Para asegurarnos que es único, empezamos viendo la dimensión del conjunto de datos
print(f"Nº de filas del conjunto de datos: {df_kaggle.shape[0]}")
# Vemos el número de valores únicos de la columna "ID"
x = df_kaggle["id"].nunique()
print(f"El número de valores únicos de ID es: {x}")
Nº de filas del conjunto de datos: 21742 El número de valores únicos de ID es: 21742
# La variable "ID" pasa a convertirse en el índice de la tabla
df_kaggle = df_kaggle.set_index(["id"])
# Ordenamos el dataframe en base al ID
df_kaggle.sort_index(axis=0, inplace=True)
df_kaggle.head(3)
| Unnamed: 0 | title | subtitle | sq_mt_built | sq_mt_useful | n_rooms | n_bathrooms | n_floors | sq_mt_allotment | latitude | ... | energy_certificate | has_parking | has_private_parking | has_public_parking | is_parking_included_in_price | parking_price | is_orientation_north | is_orientation_west | is_orientation_south | is_orientation_east | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 1 | 21741 | Piso en venta en calle San Epifanio, 9 | Imperial, Madrid | 72.0 | NaN | 2 | 2.0 | NaN | NaN | NaN | ... | A | True | NaN | NaN | True | 0.0 | NaN | NaN | NaN | NaN |
| 2 | 21740 | Chalet adosado en venta en calle Burriana, 12 | Campo de las Naciones-Corralejos, Madrid | 289.0 | NaN | 4 | 3.0 | 3.0 | 304.0 | NaN | ... | en trámite | True | NaN | NaN | True | 0.0 | False | False | True | False |
| 3 | 21739 | Piso en venta en Campo de las Naciones-Corralejos | Barajas, Madrid | 175.0 | NaN | 4 | 2.0 | NaN | NaN | NaN | ... | E | True | NaN | NaN | True | 0.0 | NaN | NaN | NaN | NaN |
3 rows × 57 columns
# Calculamos el número de registros duplicados y vemos que exactamente iguales, no hay ninguno
df_kaggle.duplicated().sum()
0
Sin embargo, si no tenemos en cuenta el ID (que vimos que era único para cada registro) y nos fijamos en el resto de campos, vemos que sí hay registros duplicados.
En este caso, creemos que un mismo inmueble puede estar publicado por dos o más agencias diferentes, lo que explicaría este comportamiento. Intentaríamos conseguir más información sobre esto, pero suponiendo que sí sea posible ya que es elección del propietario (hipótesis), consideramos eliminar los registros duplicados, quedándonos con un único ejemplo en cada caso.
# Vemos el total de registros duplicados (sin tener en cuenta el ID y la columna Unnamed: 0 que también es única)
df_kaggle.duplicated(subset=df_kaggle.columns[1:], keep=False).sum()
351
# Vemos algunos ejemplos de registros duplicados
df_kaggle[df_kaggle.duplicated(subset=df_kaggle.columns[1:], keep=False)].sort_values(by=['title']).head(6)
| Unnamed: 0 | title | subtitle | sq_mt_built | sq_mt_useful | n_rooms | n_bathrooms | n_floors | sq_mt_allotment | latitude | ... | energy_certificate | has_parking | has_private_parking | has_public_parking | is_parking_included_in_price | parking_price | is_orientation_north | is_orientation_west | is_orientation_south | is_orientation_east | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 14835 | 6907 | Casa o chalet independiente en venta en Valde... | Moncloa, Madrid | 400.0 | NaN | 5 | 6.0 | 2.0 | 2.0 | NaN | ... | en trámite | True | NaN | NaN | True | 0.0 | False | False | True | True |
| 14834 | 6908 | Casa o chalet independiente en venta en Valde... | Moncloa, Madrid | 400.0 | NaN | 5 | 6.0 | 2.0 | 2.0 | NaN | ... | en trámite | True | NaN | NaN | True | 0.0 | False | False | True | True |
| 20013 | 1729 | Casa o chalet independiente en venta en calle... | Ensanche de Vallecas - La Gavia, Madrid | 189.0 | 151.0 | 4 | 2.0 | 2.0 | NaN | NaN | ... | en trámite | True | NaN | NaN | True | 0.0 | False | False | True | False |
| 20378 | 1364 | Casa o chalet independiente en venta en calle... | Ensanche de Vallecas - La Gavia, Madrid | 189.0 | 151.0 | 4 | 2.0 | 2.0 | NaN | NaN | ... | en trámite | True | NaN | NaN | True | 0.0 | False | False | True | False |
| 20620 | 1122 | Chalet adosado en venta en calle Ferenc Puska... | El Cañaveral - Los Berrocales, Madrid | 248.0 | 225.0 | 4 | 3.0 | 3.0 | NaN | NaN | ... | no indicado | True | NaN | NaN | True | 0.0 | NaN | NaN | NaN | NaN |
| 20862 | 880 | Chalet adosado en venta en calle Ferenc Puska... | El Cañaveral - Los Berrocales, Madrid | 248.0 | 225.0 | 4 | 3.0 | 3.0 | NaN | NaN | ... | no indicado | True | NaN | NaN | True | 0.0 | NaN | NaN | NaN | NaN |
6 rows × 57 columns
# Por tanto, eliminamos los registros duplicados
# Por defecto, se mantiene la primera aparición y se elimina el resto de filas repetidas.
df_kaggle = df_kaggle.drop_duplicates(subset = df_kaggle.columns[1:])
# Nuestro dataset ahora está compuesto por:
df_kaggle.shape
(21563, 57)
##### Vemos el tipo de datos que contiene
df_kaggle.dtypes
Unnamed: 0 int64 title object subtitle object sq_mt_built float64 sq_mt_useful float64 n_rooms int64 n_bathrooms float64 n_floors float64 sq_mt_allotment float64 latitude float64 longitude float64 raw_address object is_exact_address_hidden bool street_name object street_number object portal float64 floor object is_floor_under object door float64 neighborhood_id object operation object rent_price int64 rent_price_by_area float64 is_rent_price_known bool buy_price int64 buy_price_by_area int64 is_buy_price_known bool house_type_id object is_renewal_needed bool is_new_development object built_year float64 has_central_heating object has_individual_heating object are_pets_allowed float64 has_ac object has_fitted_wardrobes object has_lift object is_exterior object has_garden object has_pool object has_terrace object has_balcony object has_storage_room object is_furnished float64 is_kitchen_equipped float64 is_accessible object has_green_zones object energy_certificate object has_parking bool has_private_parking float64 has_public_parking float64 is_parking_included_in_price object parking_price float64 is_orientation_north object is_orientation_west object is_orientation_south object is_orientation_east object dtype: object
Indicamos qué recoge cada variable:
Infiriendo qué recoge cada uno de los datos que hemos descargado a partir de la web y de la observación del dataset, creemos que sería necesario implementar varias modificaciones para transformar las variables al tipo más adecuado. Sin embargo, no lo vamos a realizar ahora, sino que vamos a seguir con los siguientes puntos y lo tendremos en cuenta si es necesario transformarlo más adelante.
Si analizamos los NA's, vemos que gran parte de las variables que estamos teniendo en cuenta tienen datos faltantes. En término porcentuales, casi supone el 44% de los datos totales, por lo que es necesaria una limpieza exhaustiva de los mismos.
# Total de datos faltantes (en %)
datos_totales = np.product(df_kaggle.shape) # 1) Total de datos en el dataset
valores_faltantes_totales = df_kaggle.isnull().sum().sum() # 2) Total de datos nulos en el dataset
porcentaje_datos_faltantes = round((valores_faltantes_totales/datos_totales) * 100, 2)
print(f"Hay un {porcentaje_datos_faltantes}% de datos faltantes en el conjunto total de nuestro dataset")
Hay un 43.76% de datos faltantes en el conjunto total de nuestro dataset
# Obtenemos el número de datos faltantes por columna (ordenando de forma que el primer valor es la columna con más NAs)
df_kaggle.isnull().sum().sort_values(ascending=False)
rent_price_by_area 21563 latitude 21563 are_pets_allowed 21563 has_private_parking 21563 is_kitchen_equipped 21563 has_public_parking 21563 door 21563 portal 21563 longitude 21563 is_furnished 21563 sq_mt_allotment 20132 n_floors 20129 has_garden 20009 has_balcony 18266 is_accessible 17541 has_green_zones 17539 has_pool 16417 street_number 15273 has_storage_room 13915 parking_price 13893 is_parking_included_in_price 13893 sq_mt_useful 13381 has_terrace 12079 built_year 11655 has_ac 10443 is_orientation_south 10310 is_orientation_east 10310 is_orientation_west 10310 is_orientation_north 10310 has_fitted_wardrobes 8293 has_central_heating 8092 has_individual_heating 8092 street_name 5870 raw_address 5431 is_exterior 3027 floor 2598 has_lift 2372 is_floor_under 1164 is_new_development 980 house_type_id 388 sq_mt_built 126 n_bathrooms 16 energy_certificate 0 has_parking 0 Unnamed: 0 0 title 0 is_buy_price_known 0 buy_price_by_area 0 buy_price 0 is_rent_price_known 0 rent_price 0 operation 0 neighborhood_id 0 is_exact_address_hidden 0 n_rooms 0 subtitle 0 is_renewal_needed 0 dtype: int64
A continuación, realizamos un estudio de estas variables para identificar la razón por la que hay datos faltantes e intentar encontrar la manera de rellenarlos. Implementaremos algunas técnicas que pueden ayudarnos con los valores perdidos, pero tenemos en cuenta que probablemente también acabarán eliminando alguna información útil o añadiendo algo de ruido a nuestros datos.
CASO 0: Hay valores faltantes en todos los registros de la variable
Este es el caso de rent_price_by_area, are_pets_allowed, has_private_parking, is_kitchen_equipped, has_public_parking, door, portal, latitude, longitude y is_furnished. Dado que no aportan ninguna información para la predicción, decidimos eliminarlas de nuestro conjunto de datos.
# Vemos que hay variables que directamente, no disponen de ningun valor
df_kaggle["rent_price_by_area"].value_counts(dropna=False)
NaN 21563 Name: rent_price_by_area, dtype: int64
# eliminamos estas columnas
df_kaggle.drop(["rent_price_by_area", "are_pets_allowed", "has_private_parking", "is_kitchen_equipped", "door",
"has_public_parking", "portal", "latitude", "longitude", "is_furnished"], axis=1, inplace=True)
CASO 1: Los valores faltantes corresponden a una categoría de la variable
df_kaggle["n_floors"].value_counts(dropna = False)
NaN 20129 4.0 567 3.0 475 2.0 314 1.0 56 5.0 21 7.0 1 Name: n_floors, dtype: int64
# Observamos que probemos el valor que probemos de la variable n_floor, sólo contempla chalets
#df_kaggle["house_type_id"].loc[df_kaggle["n_floors"]==1]
df_kaggle["house_type_id"].loc[df_kaggle["n_floors"]==2]
#df_kaggle["house_type_id"].loc[df_kaggle["n_floors"]==3]
#df_kaggle["house_type_id"].loc[df_kaggle["n_floors"]==4]
#df_kaggle["house_type_id"].loc[df_kaggle["n_floors"]==5]
#df_kaggle["house_type_id"].loc[df_kaggle["n_floors"]==7]
id
9 HouseType 2: Casa o chalet
184 HouseType 2: Casa o chalet
187 HouseType 2: Casa o chalet
189 HouseType 2: Casa o chalet
193 HouseType 2: Casa o chalet
...
20698 HouseType 2: Casa o chalet
20845 HouseType 2: Casa o chalet
20846 HouseType 2: Casa o chalet
20916 HouseType 2: Casa o chalet
21657 HouseType 2: Casa o chalet
Name: house_type_id, Length: 314, dtype: object
# Solución: podríamos convertir los valores nulos en la categoría 1
df_kaggle["n_floors"].fillna(1, inplace=True)
df_kaggle["has_garden"].value_counts(dropna=False)
NaN 20009 True 1554 Name: has_garden, dtype: int64
df_kaggle["has_balcony"].value_counts(dropna=False)
NaN 18266 True 3297 Name: has_balcony, dtype: int64
df_kaggle["has_green_zones"].value_counts(dropna=False)
NaN 17539 True 4024 Name: has_green_zones, dtype: int64
df_kaggle["is_accessible"].value_counts(dropna=False)
NaN 17541 True 4022 Name: is_accessible, dtype: int64
df_kaggle["has_pool"].value_counts(dropna=False)
NaN 16417 True 5146 Name: has_pool, dtype: int64
df_kaggle["has_storage_room"].value_counts(dropna=False)
NaN 13915 True 7648 Name: has_storage_room, dtype: int64
df_kaggle["has_terrace"].value_counts(dropna=False)
NaN 12079 True 9484 Name: has_terrace, dtype: int64
df_kaggle["has_ac"].value_counts(dropna=False)
True 11120 NaN 10443 Name: has_ac, dtype: int64
df_kaggle["has_fitted_wardrobes"].value_counts(dropna=False)
True 13270 NaN 8293 Name: has_fitted_wardrobes, dtype: int64
# Solución:
df_kaggle["has_garden"].fillna(False, inplace=True)
df_kaggle["has_balcony"].fillna(False, inplace=True)
df_kaggle["has_green_zones"].fillna(False, inplace=True)
df_kaggle["is_accessible"].fillna(False, inplace=True)
df_kaggle["has_pool"].fillna(False, inplace=True)
df_kaggle["has_storage_room"].fillna(False, inplace=True)
df_kaggle["has_terrace"].fillna(False, inplace=True)
df_kaggle["has_ac"].fillna(False, inplace=True)
df_kaggle["has_fitted_wardrobes"].fillna(False, inplace=True)
df_kaggle["is_orientation_east"].value_counts(dropna=False)
NaN 10310 False 6238 True 5015 Name: is_orientation_east, dtype: int64
df_kaggle["is_orientation_south"].value_counts(dropna=False)
NaN 10310 True 5767 False 5486 Name: is_orientation_south, dtype: int64
df_kaggle["is_orientation_west"].value_counts(dropna=False)
NaN 10310 False 7213 True 4040 Name: is_orientation_west, dtype: int64
df_kaggle["is_orientation_north"].value_counts(dropna=False)
NaN 10310 False 8485 True 2768 Name: is_orientation_north, dtype: int64
df_kaggle[["is_orientation_east", "is_orientation_south", "is_orientation_west", "is_orientation_north"]].loc[df_kaggle["is_orientation_east"].isna()].head()
| is_orientation_east | is_orientation_south | is_orientation_west | is_orientation_north | |
|---|---|---|---|---|
| id | ||||
| 1 | NaN | NaN | NaN | NaN |
| 3 | NaN | NaN | NaN | NaN |
| 5 | NaN | NaN | NaN | NaN |
| 6 | NaN | NaN | NaN | NaN |
| 7 | NaN | NaN | NaN | NaN |
# Solucion: eliminamos las variables
df_kaggle.drop(["is_orientation_east", "is_orientation_south", "is_orientation_west", "is_orientation_north"], axis=1, inplace=True)
df_kaggle["has_central_heating"].value_counts(dropna=False)
False 9420 NaN 8092 True 4051 Name: has_central_heating, dtype: int64
df_kaggle["has_individual_heating"].value_counts(dropna=False)
True 9420 NaN 8092 False 4051 Name: has_individual_heating, dtype: int64
# No obtenemos ningún patrón que nos ayude a imputar los datos a partir de alguna hipótesis
df_kaggle[["house_type_id", "has_individual_heating", "has_central_heating"]].loc[df_kaggle["has_individual_heating"].isna()].head()
| house_type_id | has_individual_heating | has_central_heating | |
|---|---|---|---|
| id | |||
| 1 | HouseType 1: Pisos | NaN | NaN |
| 2 | HouseType 2: Casa o chalet | NaN | NaN |
| 9 | HouseType 2: Casa o chalet | NaN | NaN |
| 34 | HouseType 1: Pisos | NaN | NaN |
| 35 | HouseType 1: Pisos | NaN | NaN |
# Solucion: eliminamos las variables
df_kaggle.drop(["has_individual_heating", "has_central_heating"], axis=1, inplace=True)
df_kaggle["house_type_id"].value_counts(dropna=False)
HouseType 1: Pisos 17544 HouseType 2: Casa o chalet 1934 HouseType 5: Áticos 1022 HouseType 4: Dúplex 675 NaN 388 Name: house_type_id, dtype: int64
# Solución: englobarlos en la categoría "Otros"
df_kaggle["house_type_id"].fillna("HouseType 3: Otros", inplace=True)
CASO 2: Los valores faltantes corresponden a datos no registrados por error
df_kaggle["sq_mt_allotment"].value_counts(dropna = False)
NaN 20132
1.0 281
2.0 116
3.0 56
250.0 38
...
237.0 1
728.0 1
547.0 1
259.0 1
22.0 1
Name: sq_mt_allotment, Length: 357, dtype: int64
# Solución: eliminar la variable
df_kaggle.drop(["sq_mt_allotment"], axis=1, inplace=True)
df_kaggle["street_number"].value_counts(dropna = False)
NaN 15273
4 210
3 209
6 196
1 191
...
364 1
320 1
275 1
419 1
bis 1
Name: street_number, Length: 421, dtype: int64
# Solución: eliminar la variable
df_kaggle.drop(["street_number"], axis=1, inplace=True)
df_kaggle["parking_price"].value_counts(dropna = False)
NaN 13893
0.0 7072
30000.0 60
25000.0 48
20000.0 46
...
60.0 1
78000.0 1
130.0 1
5403.0 1
14210.0 1
Name: parking_price, Length: 85, dtype: int64
# Observamos que no hay inmuebles que tengan parking y no se les haya asociado precio
df_kaggle.loc[df_kaggle["parking_price"].isna() & df_kaggle["has_parking"]==True]
| Unnamed: 0 | title | subtitle | sq_mt_built | sq_mt_useful | n_rooms | n_bathrooms | n_floors | raw_address | is_exact_address_hidden | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id |
0 rows × 39 columns
# Solución: imputar los NA por el valor 0
df_kaggle["parking_price"].fillna(0, inplace=True)
df_kaggle["is_parking_included_in_price"].value_counts(dropna = False)
NaN 13893 True 7070 False 600 Name: is_parking_included_in_price, dtype: int64
# Observamos que no hay inmuebles que tengan parking y no se les haya asociado valor de esta variable
df_kaggle.loc[df_kaggle["is_parking_included_in_price"].isna() & df_kaggle["has_parking"]==True]
| Unnamed: 0 | title | subtitle | sq_mt_built | sq_mt_useful | n_rooms | n_bathrooms | n_floors | raw_address | is_exact_address_hidden | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id |
0 rows × 39 columns
# Solución: imputar los NA por False
df_kaggle["is_parking_included_in_price"].fillna(False, inplace=True)
df_kaggle["built_year"].value_counts(dropna = False)
NaN 11655
1960.0 511
1970.0 479
1965.0 431
1900.0 429
...
1853.0 1
1861.0 1
1883.0 1
8170.0 1
1903.0 1
Name: built_year, Length: 156, dtype: int64
# Solución: eliminar la variable
df_kaggle.drop(["built_year"], axis=1, inplace=True)
df_kaggle["street_name"].value_counts(dropna = False)
NaN 5870
Paseo de la Castellana 115
Calle de Bravo Murillo 69
Calle del Príncipe de Vergara 60
Urb. Salamanca Prime 57
...
Calle POETA ANGELA FIGUERA 1
Julian Romea 1
Paseo SAN FRANCISCO DE SALES 1
Urb. Trafalgar Everyprop.com 1
Calle Pedro Jiménez 1
Name: street_name, Length: 6178, dtype: int64
df_kaggle[["street_name", "raw_address"]].loc[df_kaggle["street_name"].isna()]
| street_name | raw_address | |
|---|---|---|
| id | ||
| 3 | NaN | NaN |
| 5 | NaN | NaN |
| 21 | NaN | NaN |
| 22 | NaN | NaN |
| 23 | NaN | NaN |
| ... | ... | ... |
| 21725 | NaN | NaN |
| 21726 | NaN | NaN |
| 21731 | NaN | NaN |
| 21734 | NaN | NaN |
| 21735 | NaN | NaN |
5870 rows × 2 columns
# Solución: eliminar la variable
df_kaggle.drop(["street_name"], axis=1, inplace=True)
df_kaggle["raw_address"].value_counts(dropna = False)
NaN 5431
Paseo de la Castellana 93
Urb. Salamanca Prime 57
Calle de Bravo Murillo 41
Calle del Príncipe de Vergara 39
...
Pedro Antonio de Alarcon, 24 1
Mateo García 1
Calle Salvador de Madariaga, 54 1
Calle Yerma, 8 -10 1
Calle de Godella, 64 1
Name: raw_address, Length: 9667, dtype: int64
# Solución: eliminar la variable
df_kaggle.drop(["raw_address"], axis=1, inplace=True)
df_kaggle["is_exterior"].value_counts(dropna=False)
True 16784 NaN 3027 False 1752 Name: is_exterior, dtype: int64
# Observamos si los valores que son NA se tratan de chalets
df_kaggle[["house_type_id", "is_exterior"]].loc[df_kaggle["is_exterior"].isna()].head()
| house_type_id | is_exterior | |
|---|---|---|
| id | ||
| 2 | HouseType 2: Casa o chalet | NaN |
| 9 | HouseType 2: Casa o chalet | NaN |
| 11 | HouseType 2: Casa o chalet | NaN |
| 12 | HouseType 2: Casa o chalet | NaN |
| 17 | HouseType 1: Pisos | NaN |
# Solución: imputar los NA por la clase mayoritaria
df_kaggle["is_exterior"].fillna(True, inplace=True)
df_kaggle["has_lift"].value_counts(dropna=False)
True 14764 False 4427 NaN 2372 Name: has_lift, dtype: int64
# Observamos si los valores que son NA se tratan de chalets
df_kaggle[["house_type_id", "has_lift"]].loc[df_kaggle["has_lift"].isna()].head()
| house_type_id | has_lift | |
|---|---|---|
| id | ||
| 2 | HouseType 2: Casa o chalet | NaN |
| 9 | HouseType 2: Casa o chalet | NaN |
| 11 | HouseType 2: Casa o chalet | NaN |
| 12 | HouseType 2: Casa o chalet | NaN |
| 13 | HouseType 1: Pisos | NaN |
# Solución: imputar los NA por False
df_kaggle["has_lift"].fillna(False, inplace=True)
df_kaggle["is_floor_under"].value_counts(dropna=False)
False 17875 True 2524 NaN 1164 Name: is_floor_under, dtype: int64
# Observamos si los valores que son NA se tratan de chalets
df_kaggle[["house_type_id", "is_floor_under"]].loc[df_kaggle["is_floor_under"].isna()].head()
| house_type_id | is_floor_under | |
|---|---|---|
| id | ||
| 1 | HouseType 1: Pisos | NaN |
| 22 | HouseType 2: Casa o chalet | NaN |
| 69 | HouseType 2: Casa o chalet | NaN |
| 79 | HouseType 2: Casa o chalet | NaN |
| 89 | HouseType 2: Casa o chalet | NaN |
# Solución: imputar los NA por False
df_kaggle["is_floor_under"].fillna(False, inplace=True)
df_kaggle["is_new_development"].value_counts(dropna=False)
False 19117 True 1466 NaN 980 Name: is_new_development, dtype: int64
# Observamos que los valores que son NA se tratan de inmuebles que no necesitan reforma
df_kaggle[["is_new_development", "is_renewal_needed"]].loc[df_kaggle["is_new_development"].isna() & df_kaggle["is_renewal_needed"]==True].head()
| is_new_development | is_renewal_needed | |
|---|---|---|
| id |
# Solución: imputar los NA por True
df_kaggle["is_new_development"].fillna(True, inplace=True)
U a los inmuebles que cumplen esta condición (indicando que son viviendas unifamiliares) y 0 al resto, ya que pueden tratarse de pisos en la planta 0.df_kaggle["floor"].value_counts(dropna=False)
1 4389 2 3522 3 2975 NaN 2598 4 2301 Bajo 2130 5 1298 6 905 7 549 8 322 Entreplanta exterior 235 9 180 Semi-sótano exterior 55 Semi-sótano interior 36 Entreplanta interior 32 Sótano interior 23 Sótano 5 Sótano exterior 4 Entreplanta 3 Semi-sótano 1 Name: floor, dtype: int64
# Observamos que los valores que son NA no solo se tratan de chalets
df_kaggle[["house_type_id", "floor"]].loc[df_kaggle["floor"].isna()].head()
| house_type_id | floor | |
|---|---|---|
| id | ||
| 1 | HouseType 1: Pisos | NaN |
| 2 | HouseType 2: Casa o chalet | NaN |
| 9 | HouseType 2: Casa o chalet | NaN |
| 11 | HouseType 2: Casa o chalet | NaN |
| 12 | HouseType 2: Casa o chalet | NaN |
# Solución:
df_kaggle["floor"].loc[df_kaggle["house_type_id"]=="HouseType 2: Casa o chalet"].fillna("U", inplace=True)
df_kaggle["floor"].fillna(0, inplace=True)
df_kaggle["sq_mt_built"].value_counts(dropna=False)
70.0 491
60.0 482
80.0 424
65.0 376
90.0 354
...
652.0 1
691.0 1
984.0 1
403.0 1
16.0 1
Name: sq_mt_built, Length: 679, dtype: int64
# Observamos que hay valores de sq_mt_built que podemos imputar a partir de los sq_mt_useful
df_kaggle.loc[df_kaggle["sq_mt_built"].isna() & df_kaggle["sq_mt_useful"].notna()].head()
| Unnamed: 0 | title | subtitle | sq_mt_built | sq_mt_useful | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 11252 | 10490 | Casa o chalet independiente en venta en Conde... | Hortaleza, Madrid | NaN | 900.0 | 8 | 6.0 | 1.0 | True | 0 | ... | True | False | False | True | False | False | no indicado | True | True | 0.0 |
| 11254 | 10488 | Casa o chalet independiente en venta en Conde... | Hortaleza, Madrid | NaN | 900.0 | 7 | 5.0 | 4.0 | True | 0 | ... | True | True | False | True | False | False | F | True | True | 0.0 |
| 11513 | 10229 | Casa o chalet en venta en Conde Orgaz-Piovera | Hortaleza, Madrid | NaN | 506.0 | 5 | 5.0 | 2.0 | True | 0 | ... | True | False | False | False | False | False | E | False | False | 0.0 |
| 11903 | 9839 | Casa o chalet independiente en venta en Conde... | Hortaleza, Madrid | NaN | 970.0 | 6 | 8.0 | 4.0 | True | 0 | ... | True | True | False | True | False | False | D | True | True | 0.0 |
| 12206 | 9536 | Casa o chalet independiente en venta en aveni... | Conde Orgaz-Piovera, Madrid | NaN | 810.0 | 9 | 7.0 | 2.0 | True | 0 | ... | True | True | False | True | False | False | en trámite | True | True | 0.0 |
5 rows × 36 columns
# Solución:
# Si existen los m2 utiles, lo rellenamos con este valor
df_kaggle["sq_mt_built"].loc[df_kaggle["sq_mt_useful"].notna()].fillna(df_kaggle["sq_mt_useful"], inplace=True)
# Si no existe el dato, lo imputamos por la mediana de la variable
df_kaggle["sq_mt_built"].fillna(df_kaggle["sq_mt_built"].median(), inplace=True)
df_kaggle["sq_mt_useful"].value_counts(dropna = False)
NaN 13381
70.0 277
60.0 229
80.0 219
50.0 202
...
308.0 1
523.0 1
419.0 1
510.0 1
261.0 1
Name: sq_mt_useful, Length: 409, dtype: int64
df_kaggle[["sq_mt_useful", "sq_mt_built"]]
| sq_mt_useful | sq_mt_built | |
|---|---|---|
| id | ||
| 1 | NaN | 72.0 |
| 2 | NaN | 289.0 |
| 3 | NaN | 175.0 |
| 4 | 83.0 | 96.0 |
| 5 | NaN | 78.0 |
| ... | ... | ... |
| 21738 | 90.0 | 108.0 |
| 21739 | NaN | 64.0 |
| 21740 | 54.0 | 94.0 |
| 21741 | NaN | 70.0 |
| 21742 | 60.0 | 64.0 |
21563 rows × 2 columns
# Solución: eliminar la variable
df_kaggle.drop(["sq_mt_useful"], axis=1, inplace=True)
df_kaggle["n_bathrooms"].value_counts(dropna = False)
1.0 9007 2.0 7326 3.0 2417 4.0 1167 5.0 869 6.0 419 7.0 182 8.0 94 9.0 37 NaN 16 10.0 13 11.0 7 13.0 3 14.0 3 16.0 1 15.0 1 12.0 1 Name: n_bathrooms, dtype: int64
# Observamos que hay valores de sq_mt_built que podemos imputar a partir de los sq_mt_useful
df_kaggle.loc[df_kaggle["n_bathrooms"].isna()].head()
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 815 | 20927 | Piso en venta en paseo Imperial | Imperial, Madrid | 131.0 | 4 | NaN | 1.0 | True | Bajo | True | ... | False | False | False | False | False | False | no indicado | False | False | 0.0 |
| 1864 | 19878 | Piso en venta en calle de Castelló | Recoletos, Madrid | 81.0 | 2 | NaN | 1.0 | True | 1 | False | ... | False | False | False | False | False | False | no indicado | False | False | 0.0 |
| 3227 | 18515 | Piso en venta en Huertas-Cortes | Centro, Madrid | 125.0 | 0 | NaN | 1.0 | True | Bajo | True | ... | False | False | False | False | False | False | E | False | False | 0.0 |
| 3556 | 18186 | Piso en venta en calle de la Amnistia | Palacio, Madrid | 158.0 | 0 | NaN | 1.0 | True | 3 | False | ... | False | False | True | False | False | False | en trámite | False | False | 0.0 |
| 4210 | 17532 | Piso en venta en cuesta DE LAS DESCARGAS | Palacio, Madrid | 126.0 | 0 | NaN | 1.0 | True | 1 | False | ... | False | False | True | False | False | False | E | False | False | 0.0 |
5 rows × 35 columns
# Solución: imputar los nulos por la categoría más frecuente
df_kaggle["n_bathrooms"].fillna(1, inplace=True)
Comprobamos que se han implementado correctamente los datos y ya no tenemos ningún valor faltante en el dataset.
df_kaggle.isnull().sum()
Unnamed: 0 0 title 0 subtitle 0 sq_mt_built 0 n_rooms 0 n_bathrooms 0 n_floors 0 is_exact_address_hidden 0 floor 0 is_floor_under 0 neighborhood_id 0 operation 0 rent_price 0 is_rent_price_known 0 buy_price 0 buy_price_by_area 0 is_buy_price_known 0 house_type_id 0 is_renewal_needed 0 is_new_development 0 has_ac 0 has_fitted_wardrobes 0 has_lift 0 is_exterior 0 has_garden 0 has_pool 0 has_terrace 0 has_balcony 0 has_storage_room 0 is_accessible 0 has_green_zones 0 energy_certificate 0 has_parking 0 is_parking_included_in_price 0 parking_price 0 dtype: int64
En este punto, vamos a revisar las características de cada columna restante en el dataset, centrándonos en los valores que toma cada variable y concretamente, en si existe algún valor atípico (outlier) dentro de nuestros datos.
Estos pueden tener diferentes significados: tratarse de un error en el registro de los datos, escaparse simplemente del rango donde se concentran la mayoría de valores pero ser un dato válido o puede que sea un dato que queremos detectar y que sea el objetivo del estudio. En cada caso, definiremos la estrategia que consideras más adecuada para tratarlos.
Unnamed: 0: no conocemos si los códigos siguen un determinado patrón, pero por la naturaleza de la columna y que es equivalente al índice del dataset, no vemos necesario estudiar valores atípicos en ella.
title: del mismo modo que en el caso anterior, los valores que toma esta columna y la naturaleza que tiene, no añade información al tipo de problema que queremos cubrir, por lo que no es necesario que estudiamos sus valores atípicos. Se tratan de los títulos puestos a los anuncios de las propiedades y por ello, lo raro sería encontrar títulos que aparecezcan más de una vez.
subtitle: siguiendo los planteamientos anteriores, esta variable se registran con un alto grado de detalle y aporta unicidad a cada propiedad, por lo que no es necesario realizar un estudio de atípicos en este caso.
# Detectar valores extraños en la columna floor
df_kaggle['sq_mt_built'].value_counts()
70.0 491
60.0 482
100.0 430
80.0 424
65.0 376
...
477.0 1
989.0 1
706.0 1
329.0 1
16.0 1
Name: sq_mt_built, Length: 678, dtype: int64
col = "sq_mt_built"
fig = px.histogram(df_kaggle, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_kaggle[col])
std = np.std(df_kaggle[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Observamos las características de los inmueble cuyo tamaño supera los 900m2 para comprobar que no son atípico
df_kaggle.loc[df_kaggle["sq_mt_built"]>=900].head()
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 189 | 21553 | Casa o chalet independiente en venta en Conde... | Hortaleza, Madrid | 998.0 | 6 | 4.0 | 2.0 | True | 0 | False | ... | True | True | True | False | False | False | en trámite | True | True | 0.0 |
| 6293 | 15449 | Casa o chalet independiente en venta en El Viso | Chamartín, Madrid | 900.0 | 9 | 7.0 | 4.0 | True | 0 | False | ... | True | True | False | True | False | False | E | True | True | 0.0 |
| 6917 | 14825 | Casa o chalet independiente en venta en El Viso | Chamartín, Madrid | 900.0 | 9 | 7.0 | 4.0 | True | 0 | False | ... | True | True | False | True | False | False | en trámite | True | True | 0.0 |
| 7259 | 14483 | Casa o chalet independiente en venta en aveni... | Nueva España, Madrid | 939.0 | 8 | 9.0 | 4.0 | True | 0 | False | ... | True | True | True | True | False | False | en trámite | True | True | 0.0 |
| 7385 | 14357 | Casa o chalet independiente en venta en Nueva... | Chamartín, Madrid | 982.0 | 8 | 9.0 | 4.0 | True | 0 | False | ... | True | True | False | True | False | False | en trámite | True | True | 0.0 |
5 rows × 35 columns
# Detectar valores extraños en la columna propertyType
df_kaggle['n_rooms'].value_counts()
3 7154 2 5445 4 3338 1 2246 5 1747 6 714 0 436 7 259 8 111 9 54 10 26 11 10 12 9 13 6 24 3 15 2 14 1 18 1 16 1 Name: n_rooms, dtype: int64
col = "n_rooms"
fig = px.histogram(df_kaggle, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_kaggle[col])
std = np.std(df_kaggle[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Observamos las características de los inmuebles con 0 hab. para comprobar que no son atípicos
df_kaggle.loc[df_kaggle["n_rooms"]==0].sample(3)
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 7462 | 14280 | Estudio en venta en paseo de la Castellana, 174 | Nueva España, Madrid | 174.0 | 0 | 1.0 | 1.0 | False | 5 | False | ... | False | False | False | False | False | False | en trámite | False | False | 0.0 |
| 7136 | 14606 | Estudio en venta en calle de Canillas, 31 | Prosperidad, Madrid | 25.0 | 0 | 1.0 | 1.0 | False | 6 | False | ... | False | False | False | False | False | False | en trámite | False | False | 0.0 |
| 8506 | 13236 | Estudio en venta en calle de Arturo Soria, 55 | Concepción, Madrid | 43.0 | 0 | 2.0 | 1.0 | False | Bajo | True | ... | False | False | False | False | False | False | no indicado | False | False | 0.0 |
3 rows × 35 columns
# Observamos las características de los inmuebles con 24 hab. para comprobar que no son atípicos
df_kaggle.loc[df_kaggle["n_rooms"]==24].sample(3)
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 7452 | 14290 | Chalet adosado en venta en El Viso | Chamartín, Madrid | 682.0 | 24 | 4.0 | 4.0 | True | 0 | False | ... | False | True | False | False | False | False | en trámite | False | False | 0.0 |
| 7518 | 14224 | Casa o chalet independiente en venta en Urb. ... | Chamartín, Madrid | 682.0 | 24 | 4.0 | 4.0 | True | 0 | False | ... | False | True | False | False | False | False | C | False | False | 0.0 |
| 7169 | 14573 | Chalet adosado en venta en calle Segre, 8 | El Viso, Madrid | 750.0 | 24 | 4.0 | 4.0 | False | 0 | False | ... | False | True | True | True | False | False | en trámite | True | True | 0.0 |
3 rows × 35 columns
# Detectar valores extraños en la columna
df_kaggle['n_bathrooms'].value_counts()
1.0 9023 2.0 7326 3.0 2417 4.0 1167 5.0 869 6.0 419 7.0 182 8.0 94 9.0 37 10.0 13 11.0 7 13.0 3 14.0 3 16.0 1 15.0 1 12.0 1 Name: n_bathrooms, dtype: int64
col = "n_bathrooms"
fig = px.histogram(df_kaggle, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_kaggle[col])
std = np.std(df_kaggle[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Observamos las características de los inmuebles con 24 hab. para comprobar que no son atípicos
df_kaggle.loc[df_kaggle["n_bathrooms"]>=12].sample(3)
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 13727 | 8015 | Casa o chalet independiente en venta en calle... | El Plantío, Madrid | 100.0 | 8 | 13.0 | 1.0 | True | 0 | False | ... | True | True | False | True | False | False | en trámite | True | True | 0.0 |
| 14661 | 7081 | Casa o chalet en venta en El Plantío | Moncloa, Madrid | 100.0 | 10 | 12.0 | 1.0 | True | 0 | False | ... | True | True | False | False | False | False | en trámite | True | True | 0.0 |
| 17177 | 4565 | Piso en venta en Pacífico | Retiro, Madrid | 620.0 | 7 | 14.0 | 1.0 | True | 1 | False | ... | False | False | False | False | False | False | en trámite | False | False | 0.0 |
3 rows × 35 columns
# Detectar valores extraños en la columna
df_kaggle['n_floors'].value_counts()
1.0 20185 4.0 567 3.0 475 2.0 314 5.0 21 7.0 1 Name: n_floors, dtype: int64
# Observamos las características del inmueble para comprobar que no es atípico
df_kaggle.loc[df_kaggle["n_floors"]==7]
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 6250 | 15492 | Casa o chalet independiente en venta en Nueva... | Chamartín, Madrid | 100.0 | 9 | 9.0 | 7.0 | True | 0 | False | ... | False | True | False | False | True | False | en trámite | True | True | 0.0 |
1 rows × 35 columns
col = "n_floors"
fig = px.histogram(df_kaggle, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_kaggle[col])
std = np.std(df_kaggle[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Detectar valores extraños en la columna
df_kaggle['is_exact_address_hidden'].value_counts()
True 14834 False 6729 Name: is_exact_address_hidden, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['floor'].value_counts()
1 4389 2 3522 3 2975 0 2598 4 2301 Bajo 2130 5 1298 6 905 7 549 8 322 Entreplanta exterior 235 9 180 Semi-sótano exterior 55 Semi-sótano interior 36 Entreplanta interior 32 Sótano interior 23 Sótano 5 Sótano exterior 4 Entreplanta 3 Semi-sótano 1 Name: floor, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['is_floor_under'].value_counts()
False 19039 True 2524 Name: is_floor_under, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['neighborhood_id'].value_counts()
Neighborhood 23: Malasaña-Universidad (5196.25 €/m2) - District 4: Centro 470
Neighborhood 59: Conde Orgaz-Piovera (4275.46 €/m2) - District 9: Hortaleza 469
Neighborhood 28: El Viso (6255.45 €/m2) - District 5: Chamartín 466
Neighborhood 32: Almagro (6564.27 €/m2) - District 6: Chamberí 420
Neighborhood 72: Aravaca (3600.4 €/m2) - District 11: Moncloa 416
...
Neighborhood 80: Horcajo (None €/m2) - District 12: Moratalaz 9
Neighborhood 9: Campo de las Naciones-Corralejos (3417.44 €/m2) - District 2: Barajas 9
Neighborhood 11: Timón (2879.88 €/m2) - District 2: Barajas 6
Neighborhood 10: Casco Histórico de Barajas (3100.87 €/m2) - District 2: Barajas 6
Neighborhood 65: Cuatro Vientos (None €/m2) - District 10: Latina 2
Name: neighborhood_id, Length: 126, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['operation'].value_counts()
sale 21563 Name: operation, dtype: int64
col = "rent_price"
fig = px.histogram(df_kaggle, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_kaggle[col])
std = np.std(df_kaggle[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Detectar valores extraños en la columna
df_kaggle['is_rent_price_known'].value_counts()
False 21563 Name: is_rent_price_known, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['buy_price'].value_counts()
550000 164
135000 148
650000 146
175000 136
160000 136
...
554900 1
942000 1
2340000 1
2320000 1
144247 1
Name: buy_price, Length: 2403, dtype: int64
col = "buy_price"
fig = px.histogram(df_kaggle, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_kaggle[col])
std = np.std(df_kaggle[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Observamos las características de los inmuebles para comprobar que no son atípicos
df_kaggle.loc[df_kaggle["buy_price"] >= 8000000].sample(5)
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 6479 | 15263 | Casa o chalet independiente en venta en Urb. ... | Chamartín, Madrid | 860.0 | 6 | 5.0 | 3.0 | True | 0 | False | ... | True | True | False | True | False | False | en trámite | True | True | 0.0 |
| 6548 | 15194 | Casa o chalet independiente en venta en plaza... | El Viso, Madrid | 856.0 | 7 | 7.0 | 2.0 | False | 0 | False | ... | False | True | True | True | True | False | en trámite | True | True | 0.0 |
| 17472 | 4270 | Piso en venta en Jerónimos | Retiro, Madrid | 450.0 | 4 | 5.0 | 1.0 | True | 4 | False | ... | False | False | False | True | False | False | en trámite | False | False | 0.0 |
| 6955 | 14787 | Casa o chalet independiente en venta en El Viso | Chamartín, Madrid | 850.0 | 6 | 8.0 | 3.0 | True | 0 | False | ... | False | True | False | True | True | False | en trámite | True | True | 0.0 |
| 17470 | 4272 | Ático en venta en Jerónimos | Retiro, Madrid | 700.0 | 5 | 5.0 | 1.0 | True | 6 | False | ... | False | True | False | True | False | False | E | False | False | 0.0 |
5 rows × 35 columns
# Detectar valores extraños en la columna
df_kaggle['buy_price_by_area'].value_counts()
5000 143
2000 97
3000 96
2500 90
4000 82
...
4880 1
4089 1
5437 1
6981 1
1328 1
Name: buy_price_by_area, Length: 5643, dtype: int64
col = "buy_price_by_area"
fig = px.histogram(df_kaggle, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_kaggle[col])
std = np.std(df_kaggle[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Observamos las características de los inmuebles para comprobar que no son atípicos
df_kaggle.loc[df_kaggle["buy_price_by_area"]>=16000]
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 14515 | 7227 | Piso en venta en calle Irun, 15 | Argüelles, Madrid | 65.0 | 2 | 2.0 | 1.0 | False | 8 | False | ... | True | True | False | True | False | False | no indicado | False | False | 0.0 |
| 14523 | 7219 | Piso en venta en calle Irun, 15 | Argüelles, Madrid | 39.0 | 1 | 1.0 | 1.0 | False | 8 | False | ... | True | True | False | True | False | False | no indicado | False | False | 0.0 |
| 17472 | 4270 | Piso en venta en Jerónimos | Retiro, Madrid | 450.0 | 4 | 5.0 | 1.0 | True | 4 | False | ... | False | False | False | True | False | False | en trámite | False | False | 0.0 |
| 17587 | 4155 | Piso en venta en Urb. Jeronimos Prime, Jerónimos | Retiro, Madrid | 345.0 | 4 | 3.0 | 1.0 | True | 4 | False | ... | False | False | False | True | False | False | no indicado | False | False | 0.0 |
4 rows × 35 columns
# Detectar valores extraños en la columna
df_kaggle['is_buy_price_known'].value_counts()
True 21563 Name: is_buy_price_known, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['house_type_id'].value_counts()
HouseType 1: Pisos 17544 HouseType 2: Casa o chalet 1934 HouseType 5: Áticos 1022 HouseType 4: Dúplex 675 HouseType 3: Otros 388 Name: house_type_id, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['is_renewal_needed'].value_counts()
False 17598 True 3965 Name: is_renewal_needed, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['is_new_development'].value_counts()
False 19117 True 2446 Name: is_new_development, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_ac'].value_counts()
True 11120 False 10443 Name: has_ac, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_fitted_wardrobes'].value_counts()
True 13270 False 8293 Name: has_fitted_wardrobes, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_lift'].value_counts()
True 14764 False 6799 Name: has_lift, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['is_exterior'].value_counts()
True 19811 False 1752 Name: is_exterior, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_garden'].value_counts()
False 20009 True 1554 Name: has_garden, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_pool'].value_counts()
False 16417 True 5146 Name: has_pool, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_terrace'].value_counts()
False 12079 True 9484 Name: has_terrace, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_balcony'].value_counts()
False 18266 True 3297 Name: has_balcony, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_storage_room'].value_counts()
False 13915 True 7648 Name: has_storage_room, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['is_accessible'].value_counts()
False 17541 True 4022 Name: is_accessible, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_green_zones'].value_counts()
False 17539 True 4024 Name: has_green_zones, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['energy_certificate'].value_counts()
en trámite 10815 no indicado 3657 E 2681 D 1111 G 892 F 672 A 616 C 575 B 449 inmueble exento 95 Name: energy_certificate, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['has_parking'].value_counts()
False 13893 True 7670 Name: has_parking, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['is_parking_included_in_price'].value_counts()
False 14493 True 7070 Name: is_parking_included_in_price, dtype: int64
# Detectar valores extraños en la columna
df_kaggle['parking_price'].value_counts()
0.0 20965
30000.0 60
25000.0 48
20000.0 46
15000.0 42
...
110.0 1
60.0 1
90000.0 1
48500.0 1
14210.0 1
Name: parking_price, Length: 84, dtype: int64
# Eliminamos el precio 0€
df_aux = df_kaggle.loc[df_kaggle["parking_price"] > 0]
col = "parking_price"
fig = px.histogram(df_aux, x=col, title=f"Histograma de la columna {col}")
# Utilizamos la media y un múltiplo de la desviación estándar para establecer los límites superior e inferior
mean = np.mean(df_aux[col])
std = np.std(df_aux[col])
atipicos_leves_sup = mean + 1.5*std
atipicos_leves_inf = mean - 1.5*std
fig.add_vrect(x0=atipicos_leves_inf, x1=atipicos_leves_sup, annotation_text="tipicos", annotation_position="top left",
fillcolor="green", opacity=0.25, line_width=0)
fig.show()
# Observamos las características de los inmueble con alto precio de la plaza de parking
df_kaggle.loc[df_kaggle["parking_price"] > 350000]
| Unnamed: 0 | title | subtitle | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | ... | has_pool | has_terrace | has_balcony | has_storage_room | is_accessible | has_green_zones | energy_certificate | has_parking | is_parking_included_in_price | parking_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 14616 | 7126 | Ático en venta en avenida valladolid | Casa de Campo, Madrid | 500.0 | 12 | 4.0 | 1.0 | True | 7 | False | ... | True | True | False | False | False | False | en trámite | True | False | 600000.0 |
| 15144 | 6598 | Piso en venta en calle Valmojado, 77 | Aluche, Madrid | 132.0 | 4 | 2.0 | 1.0 | False | 4 | False | ... | False | True | False | True | False | True | no indicado | True | False | 380000.0 |
2 rows × 35 columns
Atendiendo a la estadística descriptiva, vemos que el tamaño medio construido de las propiedades es en torno a los 145 m2, encontrando que el registro más pequeño tiene tan solo 13 m2, mientras que el mayor casi alcanza los 1.000 m2. Por otra parte, cabe destacar que las propiedades en Madrid se componen por 1 piso con 3 habitaciones y 2 baños, en mediana.
El precio medio de venta de las propiedades supera los 650.000€, alcanzando el inmueble más caro un precio de casi 9M, mientras que el más barato se vende por 36.000€. Por su parte, la plaza de parking ronda un precio medio de 940€. Resalta también el dato de que en Madrid, el precio medio de venta por zona supera ligeramente los 4.000 €/m2.
## Vemos la estadística de las variables numéricas
df_kaggle.describe().T
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| Unnamed: 0 | 21563.0 | 10866.735380 | 6272.730650 | 0.0 | 5441.5 | 10859.0 | 16290.5 | 21741.0 |
| sq_mt_built | 21563.0 | 146.963873 | 134.196800 | 13.0 | 70.0 | 100.0 | 162.0 | 999.0 |
| n_rooms | 21563.0 | 3.008023 | 1.512641 | 0.0 | 2.0 | 3.0 | 4.0 | 24.0 |
| n_bathrooms | 21563.0 | 2.093540 | 1.409719 | 1.0 | 1.0 | 2.0 | 2.0 | 16.0 |
| n_floors | 21563.0 | 1.141678 | 0.580109 | 1.0 | 1.0 | 1.0 | 1.0 | 7.0 |
| rent_price | 21563.0 | -59660.618235 | 920898.291004 | -34590276.0 | 724.5 | 1113.0 | 1687.0 | 2517.0 |
| buy_price | 21563.0 | 655387.474748 | 784362.806664 | 36000.0 | 198000.0 | 375000.0 | 766000.0 | 8800000.0 |
| buy_price_by_area | 21563.0 | 4019.520104 | 1909.829163 | 447.0 | 2548.5 | 3719.0 | 5000.0 | 18889.0 |
| parking_price | 21563.0 | 946.487316 | 8083.082188 | 0.0 | 0.0 | 0.0 | 0.0 | 600000.0 |
Por otra parte, el perfil del inmueble prototipo que encontramos es un piso en venta, de una planta y ubicado en el barrio de Malasaña-Universidad, aunque cuya dirección exacta está oculta. No tiene ningún piso vecino en la planta de abajo, su certificación energética está en trámite y a pesar de que no es de obra nueva, tampoco necesita reforma.
También se sabe que es exterior, tiene aire acondicionado, armarios empotrados y ascensor, pero no cuenta con plaza de aparcamiento, jardín, zonas verdes, piscina, terraza ni balcón, así como tampoco tiene una habitación habilitada como espacio de almacenamiento ni se considera un piso accesible.
# Vemos la estadística de las variables categóricas
df_kaggle.describe(include = (object, bool)).T
| count | unique | top | freq | |
|---|---|---|---|---|
| title | 21563 | 10736 | Piso en venta en El Viso | 192 |
| subtitle | 21563 | 146 | Chamartín, Madrid | 847 |
| is_exact_address_hidden | 21563 | 2 | True | 14834 |
| floor | 21563 | 20 | 1 | 4389 |
| is_floor_under | 21563 | 2 | False | 19039 |
| neighborhood_id | 21563 | 126 | Neighborhood 23: Malasaña-Universidad (5196.25... | 470 |
| operation | 21563 | 1 | sale | 21563 |
| is_rent_price_known | 21563 | 1 | False | 21563 |
| is_buy_price_known | 21563 | 1 | True | 21563 |
| house_type_id | 21563 | 5 | HouseType 1: Pisos | 17544 |
| is_renewal_needed | 21563 | 2 | False | 17598 |
| is_new_development | 21563 | 2 | False | 19117 |
| has_ac | 21563 | 2 | True | 11120 |
| has_fitted_wardrobes | 21563 | 2 | True | 13270 |
| has_lift | 21563 | 2 | True | 14764 |
| is_exterior | 21563 | 2 | True | 19811 |
| has_garden | 21563 | 2 | False | 20009 |
| has_pool | 21563 | 2 | False | 16417 |
| has_terrace | 21563 | 2 | False | 12079 |
| has_balcony | 21563 | 2 | False | 18266 |
| has_storage_room | 21563 | 2 | False | 13915 |
| is_accessible | 21563 | 2 | False | 17541 |
| has_green_zones | 21563 | 2 | False | 17539 |
| energy_certificate | 21563 | 10 | en trámite | 10815 |
| has_parking | 21563 | 2 | False | 13893 |
| is_parking_included_in_price | 21563 | 2 | False | 14493 |
# Vemos la distribución de la variable a predecir
fig = px.histogram(df_kaggle, x="buy_price", title=f"Distribución del precio de venta de los inmuebles")
fig.show()
A continuación, decidimos realizar varios gráficos que nos representen a simple vista, los datos que hemos ido analizando.
# Grafico 1: proporción de inmuebles interior / exterior
sns.catplot(x="is_exterior", kind="count", data=df_kaggle, height=3, aspect=2)
plt.show()
# Grafico 2: proporción de inmuebles de nueva construcción
sns.catplot(x="is_new_development", kind="count", data=df_kaggle, height=3, aspect=2)
plt.show()
# Grafico 3: proporción de inmuebles con ascensor
sns.catplot(x="has_lift", kind="count", data=df_kaggle, height=3, aspect=2)
plt.show()
# Grafico 4: proporción de inmuebles según el tipo al que pertenecen
ax = sns.catplot(x="house_type_id", kind="count", data=df_kaggle, height=3, aspect=2)
plt.xticks(rotation=90)
plt.show()
# Grafico 5: barrios más comunes
# Para ello, primero vamos a eliminar ", Madrid" al final de los valores en la columna 'subtitle'
df_kaggle['subtitle'] = df_kaggle['subtitle'].str.replace(r', Madrid$', '')
# Cambiamos el nombre de la columna 'subtitle' a 'neighborhood'
df_kaggle = df_kaggle.rename(columns={'subtitle': 'neighborhood'})
### AHORA SI, VEAMOS EL NUMERO DE INMUEBLES EN VENTA POR CADA UBICACION
# Usamos value_counts() para contar los inmuebles por ubicación (neighborhood)
conteo_ubicaciones = df_kaggle['neighborhood'].value_counts()
# Creamos un nuevo DataFrame a partir de conteo_ubicaciones para poder definir un Pareto para generar un ploteo que muestre
# las ubicaciones con más inmuebles ya que hay muchas zonas (146) para que se lean bien en un grafico de barras
df_ubicaciones = pd.DataFrame({'Ubicacion': conteo_ubicaciones.index, 'Cantidad': conteo_ubicaciones.values})
# Calculamos el acumulado de mayor a menor
df_ubicaciones['Acumulado'] = df_ubicaciones['Cantidad'].cumsum()
# Calculamos el porcentaje sobre el total
total_inmuebles = len(df_kaggle)
df_ubicaciones['Porcentaje'] = (df_ubicaciones['Cantidad'] / total_inmuebles) * 100
# Redondeamos la columna 'Porcentaje' a dos decimales
df_ubicaciones['Porcentaje'] = df_ubicaciones['Porcentaje'].round(2)
# Calculamos el porcentaje acumulado
df_ubicaciones['Porcentaje Acumulado'] = (df_ubicaciones['Acumulado'] / total_inmuebles) * 100
# Redondear la columna 'Porcentaje Acumulado' a dos decimales
df_ubicaciones['Porcentaje Acumulado'] = df_ubicaciones['Porcentaje Acumulado'].round(2)
# Ordenamos el DataFrame por cantidad (mayor a menor)
df_ubicaciones = df_ubicaciones.sort_values(by='Cantidad', ascending=False)
# Reiniciamos el índice del DataFrame
df_ubicaciones = df_ubicaciones.reset_index(drop=True)
# Mostramos el DataFrame resultante
print(df_ubicaciones)
Ubicacion Cantidad Acumulado Porcentaje \
0 Chamartín 847 847 3.93
1 Moncloa 768 1615 3.56
2 Chamberí 579 2194 2.69
3 Centro 546 2740 2.53
4 Hortaleza 502 3242 2.33
.. ... ... ... ...
141 Barajas 7 21546 0.03
142 Campo de las Naciones-Corralejos 6 21552 0.03
143 Casco Histórico de Barajas 5 21557 0.02
144 Timón 4 21561 0.02
145 Cuatro Vientos 2 21563 0.01
Porcentaje Acumulado
0 3.93
1 7.49
2 10.17
3 12.71
4 15.04
.. ...
141 99.92
142 99.95
143 99.97
144 99.99
145 100.00
[146 rows x 5 columns]
C:\Users\Natalia\AppData\Local\Temp\ipykernel_13276\2917814526.py:4: FutureWarning: The default value of regex will change from True to False in a future version.
# Como hay tantas ubicaciones (146), vamos a mostrar solo el 70% de las localidades con más inmuebles
# ¿Porque tomamos 70%? Porque visualmente es el mayor valor que puede apreciarse de buena manera en el grafico
# Hallamos el percentil 80 basado en el Porcentaje Acumulado
percentil_70 = np.percentile(df_ubicaciones['Porcentaje Acumulado'], 70)
# Filtramos el Dataset para incluir solo los valores superiores al percentil 70
df_top_70 = df_ubicaciones[df_ubicaciones['Porcentaje Acumulado'] <= percentil_70]
# Personalizamos el gráfico
# Ajustar el tamaño del gráfico
fig, ax = plt.subplots(figsize=(15, 6))
# Establecer el color de las barras
df_top_70.plot(kind='bar', x='Ubicacion', y='Cantidad', color='#00008B', ax=ax)
# Agregar etiquetas al gráfico
ax.set_title('Total de inmuebles por ubicación')
ax.set_xlabel('Ubicación')
ax.set_ylabel('Total de inmuebles')
plt.show()
# Grafico 6: distritos más comunes
# Nos quedamos con el distrito, que aparece al final de cada texto
distrito = [e.split(": ")[-1] for e in list(df_kaggle["neighborhood_id"])]
# Creo la variable "district"
df_kaggle["district"] = distrito
ax = sns.catplot(x="district", kind="count", data=df_kaggle, height=3, aspect=2)
plt.xticks(rotation=90)
plt.show()
# Grafico 7: precio medio por distrito
plt.figure(figsize=(12,6))
barrios = df_kaggle.groupby("district")["buy_price"].mean()
barrios = barrios.sort_values(ascending=False)
barrios.plot.bar()
plt.show()
Para algunos algoritmos de Machine Learning, será necesario tener las variables categóricas en formato numérico. Para ello, en este punto, vamos a utilizar diferentes técnicas para la discretización de las columnas no numéricas, para poder ser tratadas por estos algoritmos. Para ello, habiendo visto cómo se distribuyen los valores de cada una de las variables, las podemos discretizar de diferente forma.
En nuestro caso, las variables categóricas son:
df_kaggle.dtypes
Unnamed: 0 int64 title object neighborhood object sq_mt_built float64 n_rooms int64 n_bathrooms float64 n_floors float64 is_exact_address_hidden bool floor object is_floor_under bool neighborhood_id object operation object rent_price int64 is_rent_price_known bool buy_price int64 buy_price_by_area int64 is_buy_price_known bool house_type_id object is_renewal_needed bool is_new_development bool has_ac bool has_fitted_wardrobes bool has_lift bool is_exterior bool has_garden bool has_pool bool has_terrace bool has_balcony bool has_storage_room bool is_accessible bool has_green_zones bool energy_certificate object has_parking bool is_parking_included_in_price bool parking_price float64 district object dtype: object
# Antes de comenzar, creamos una copia del df
df_clean_kaggle = df_kaggle.copy()
# Esto se aplica sobre las variables: is_exact_address_hidden, is_floor_under, is_rent_price_known, is_buy_price_known,
# is_renewal_needed, is_new_development, has_ac, has_fitted_wardrobes, has_lift, is_exterior, has_garden, has_pool,
# has_terrace, has_balcony, has_storage_room, is_accessible, has_green_zones, has_parking, is_parking_included_in_price
df_kaggle_discretizado = df_kaggle.replace({False: 0, True: 1})
#### Variable "floor"
# cuenta con valores que ya son numéricos, pero otros (que designan el sótano, semi-sótano, bajo, entreplanta y
# vivienda unifamiliar=0) son caracteres. Por ello, lo que podemos hacer es crear un diccionario únicamente para
# sustituir estos valores y así tener esta variable numérica.
# Siguiendo el ejemplo del dataset discretizado de la API, lo unificamos al mismo formato. Además, no hacemos
# distinción entre interior o exterior
df_kaggle_discretizado["floor"].replace({"Sótano exterior": 24, "Sótano interior": 24, "Sótano": 24,
"Semi-sótano exterior": 25, "Semi-sótano interior": 25, "Semi-sótano": 25,
"Bajo": 26,
"Entreplanta exterior": 27, "Entreplanta interior": 27, "Entreplanta": 27,
0: 28}, inplace=True)
# Convierto la columna a entero
df_kaggle_discretizado["floor"] = df_kaggle_discretizado["floor"].astype("int64")
#### Variable "neighborhood"
# Después de haber tratado la columna "subtitle" para obtener "neighborhood", tenemos que convertirlo a numérico
# Para modelarlo, le asignamos un número a cada valor actual.
# Ya disponíamos de esta variable en la API también (por lo que allí aplicaremos la misma asignación barrio - número que aquí)
# Vemos cómo se distribuyen los valores de la variable
categorias = (df_kaggle_discretizado[["neighborhood"]].value_counts())
# Empezamos definiendo una lista con las categorías actuales
categorias_actuales = [cat[0] for cat in categorias.index]
# Creamos las nuevas categorías, que van a corresponder con las respectivas posiciones
nuevas_categorias = [elem for elem in range(0, len(categorias_actuales))]
# Creamos el diccionario
my_mapping = dict()
for col_name, new_name in zip(categorias_actuales, nuevas_categorias):
my_mapping[col_name] = new_name
my_mapping
# Por último, sustituimos los valores de la columna por nuestro mapeo
df_kaggle_discretizado["neighborhood"] = df_kaggle_discretizado["neighborhood"].replace(my_mapping)
#### Variable "district"
# Convertimos la variable a numérico. Es decir, le asignamos un número a cada valor actual.
# Ya disponíamos de esta variable en la API también (por lo que podríamos aplicar la misma asignación barrio - número que aquí)
# Sin embargo, al no tratare de demasiados valores, decidimos aplicar One-hot encoding (a continuación)
# Partimos del mapping generado para la API (que tenía los 21 distritos, en Kaggle sólo tenemos 20)
mapping_district_API = {'Carabanchel': 0, 'Chamberí': 1, 'Centro': 2, 'Chamartín': 3, 'Arganzuela': 4,
'Ciudad Lineal': 5, 'Tetuán': 6, 'Fuencarral': 7, 'Retiro': 8, 'Barrio de Salamanca': 9,
'Hortaleza': 10, 'Puente de Vallecas': 11, 'San Blas': 12, 'Usera': 13, 'Moncloa': 14,
'Latina': 15, 'Villaverde': 16, 'Villa de Vallecas': 17, 'Vicálvaro': 18, 'Barajas': 19,
'Moratalaz': 20}
# Por tanto, sólo tenemos que sustituir en este caso los valores de la columna por nuestro mapeo
df_kaggle_discretizado["district"] = df_kaggle_discretizado["district"].replace(mapping_district_API)
#### Variable "house_type_id"
# Eliminamos "HouseType " al inicio de los valores en la columna para quedarnos con el tipo de vivienda
df_kaggle_discretizado['house_type_id'] = df_kaggle_discretizado['house_type_id'].str.replace(r'^[^:]+:\s*', '', regex=True)
df_kaggle_discretizado[["house_type_id"]].value_counts(dropna=False)
# Creamos las dummies derivadas de esta variable.
y = pd.get_dummies(df_kaggle_discretizado["house_type_id"], prefix="Type")
# Añadimos al data frame original estas nuevas columnas que hemos creado
df_kaggle_discretizado = pd.concat((df_kaggle_discretizado, y), axis=1)
#### Variable "energy_certificate"
df_kaggle_discretizado[["energy_certificate"]].value_counts(dropna=False)
# Creamos las dummies derivadas de esta variable.
y = pd.get_dummies(df_kaggle_discretizado["energy_certificate"], prefix="Energy")
# Añadimos al data frame original estas nuevas columnas que hemos creado
df_kaggle_discretizado = pd.concat((df_kaggle_discretizado, y), axis=1)
#### Variable "district"
df_kaggle_discretizado[["district"]].value_counts(dropna=False)
# Creamos las dummies derivadas de esta variable.
y2 = pd.get_dummies(df_kaggle_discretizado["district"], prefix="district")
# Añadimos al data frame original estas nuevas columnas que hemos creado
df_kaggle_discretizado = pd.concat((df_kaggle_discretizado, y2), axis=1)
df_kaggle_discretizado.dtypes
Unnamed: 0 int64
title object
neighborhood int64
sq_mt_built float64
n_rooms int64
...
district_Tetuán uint8
district_Usera uint8
district_Vicálvaro uint8
district_Villa de Vallecas uint8
district_Villaverde uint8
Length: 71, dtype: object
# De este df discretizado, eliminamos las columnas originales de las que hemos creado las dummies (o en el caso de neighborhood_id, hemos sacado la variable district)
# "District" no lo eliminamos para tenerlo en cuenta en la visualización
df_kaggle_discretizado.drop(['house_type_id', 'energy_certificate', 'neighborhood_id'], axis=1, inplace=True)
# Eliminamos también las variables que no nos aportan información
df_kaggle_discretizado.drop(['Unnamed: 0', 'title', 'operation', 'is_rent_price_known', 'is_buy_price_known'], axis=1, inplace=True)
Por último, mostramos cómo ha quedado el dataframe, viendo que las columnas dummies se han añadido al final:
# Mostramos el dataframe
df_kaggle_discretizado.head()
| neighborhood | sq_mt_built | n_rooms | n_bathrooms | n_floors | is_exact_address_hidden | floor | is_floor_under | rent_price | buy_price | ... | district_Moncloa | district_Moratalaz | district_Puente de Vallecas | district_Retiro | district_Salamanca | district_Tetuán | district_Usera | district_Vicálvaro | district_Villa de Vallecas | district_Villaverde | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 1 | 64 | 72.0 | 2 | 2.0 | 1.0 | 0 | 28 | 0 | 1494 | 424000 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 142 | 289.0 | 4 | 3.0 | 3.0 | 0 | 28 | 0 | 2115 | 695000 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 141 | 175.0 | 4 | 2.0 | 1.0 | 1 | 26 | 1 | 2081 | 680000 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 4 | 141 | 96.0 | 2 | 2.0 | 1.0 | 1 | 3 | 0 | 1496 | 425000 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 5 | 141 | 78.0 | 2 | 2.0 | 1.0 | 1 | 4 | 0 | 1323 | 350000 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 63 columns
Dado que train (df_Kaggle) y test (df_API) deben tener el mismo esqueleto, tenemos que realizar una serie de operaciones.
Este dataset con datos extraídos de Kaggle, va a ser el dataset de entrenamiento + validación.
df_train_val = df_kaggle_discretizado.drop(['neighborhood', 'n_floors', 'is_exact_address_hidden', 'is_floor_under',
'rent_price', 'has_ac', 'has_fitted_wardrobes', 'has_garden', 'has_pool',
'has_terrace', 'has_balcony', 'has_storage_room', 'is_accessible',
'has_green_zones', 'is_parking_included_in_price','parking_price',
'Energy_A', 'Energy_B', 'Energy_C', 'Energy_D', 'Energy_E', 'Energy_F',
'Energy_G','Energy_en trámite', 'Energy_inmueble exento',
'Energy_no indicado'], axis=1)
df_train_val = df_train_val.rename(columns={'title': 'description',
'sq_mt_built': 'size',
'buy_price': 'price',
'n_rooms': 'rooms',
'n_bathrooms': 'bathrooms',
'is_exterior': 'exterior',
'has_lift': 'hasLift',
'has_parking': 'parkingSpace',
'buy_price_by_area': 'priceByArea',
'Type_Casa o chalet': 'Type_chalet',
'Type_Dúplex': 'Type_duplex',
'Type_Pisos': 'Type_flat',
'Type_Otros': 'Type_studio',
'is_new_development': 'status_newdevelopment',
'is_renewal_needed': 'status_renew'})
# La variable booleana "status_good" que existe en el df de API es el complemento de 'status_newdevelopment'
# y de "status_renew". Por tanto, cuando estas dos variables sean 0, "status_good" será 1.
lista = []
for i in range(df_train_val.shape[0]):
if (df_train_val["status_newdevelopment"].iloc[i]==0) & (df_train_val["status_renew"].iloc[i]==0):
lista.append(1)
else:
lista.append(0)
df_train_val['status_good'] = lista
df_train_val = df_train_val.rename(columns={'district_Salamanca': 'district_Barrio de Salamanca'})
df_train_val['bathrooms'] = df_train_val['bathrooms'].astype('int64')
print(df_train_val.shape)
df_train_val.head()
(21563, 38)
| size | rooms | bathrooms | floor | price | priceByArea | status_renew | status_newdevelopment | hasLift | exterior | ... | district_Moratalaz | district_Puente de Vallecas | district_Retiro | district_Barrio de Salamanca | district_Tetuán | district_Usera | district_Vicálvaro | district_Villa de Vallecas | district_Villaverde | status_good | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||||||||
| 1 | 72.0 | 2 | 2 | 28 | 424000 | 5889 | 0 | 1 | 1 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 289.0 | 4 | 3 | 28 | 695000 | 2405 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 3 | 175.0 | 4 | 2 | 26 | 680000 | 3886 | 0 | 0 | 0 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 4 | 96.0 | 2 | 2 | 3 | 425000 | 4427 | 0 | 0 | 1 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 5 | 78.0 | 2 | 2 | 4 | 350000 | 4487 | 0 | 0 | 1 | 1 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
5 rows × 38 columns
df_train_val.dtypes
size float64 rooms int64 bathrooms int64 floor int64 price int64 priceByArea int64 status_renew int64 status_newdevelopment int64 hasLift int64 exterior int64 parkingSpace int64 district object Type_chalet uint8 Type_duplex uint8 Type_studio uint8 Type_flat uint8 Type_Áticos uint8 district_Arganzuela uint8 district_Barajas uint8 district_Carabanchel uint8 district_Centro uint8 district_Chamartín uint8 district_Chamberí uint8 district_Ciudad Lineal uint8 district_Fuencarral uint8 district_Hortaleza uint8 district_Latina uint8 district_Moncloa uint8 district_Moratalaz uint8 district_Puente de Vallecas uint8 district_Retiro uint8 district_Barrio de Salamanca uint8 district_Tetuán uint8 district_Usera uint8 district_Vicálvaro uint8 district_Villa de Vallecas uint8 district_Villaverde uint8 status_good int64 dtype: object
df_train_val.to_csv("df_train_val.csv")
Ya estamos en disposición de entrenar modelos de ML para predecir los precios de los inmuebles.